#include "timestamp.h"

/**
 * @brief 计算周
 *
 * @param Year     - 年
 * @param Month    - 月
 * @param Day      - 日
 * @return uint8_t - 当前周期
 */
static uint8_t ComputingWeek(uint16_t Year, uint8_t Month, uint8_t Day);

/**
 * @brief 判断是否为闰年
 *
 * @param Year  - 年
 * @return bool - 闰年（true：是 false：否）
 */
static bool DetermineWhetherIsLeapYear(uint16_t Year);

/**
 * @brief 获取当月总天数
 *
 * @param Month    - 需要得到天数的月份数
 * @param Year     - 该月所对应的年份数
 * @return uint8_t - 总天数
 */
static uint8_t ObtainTheTotalNumberOfDaysInTheMonth(uint8_t Month, uint16_t Year);

/**
 * @brief 获取当年总天数
 *
 * @param Year      - 年份
 * @return uint32_t - 总天数
 */
static uint32_t ObtainTheTotalNumberOfDaysInTheYear(uint16_t Year);

const uint8_t g_day_per_mon[MONTH_PER_YEAR] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
const uint8_t Leap_month_day[MONTH_PER_YEAR] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};   // 闰年
const uint8_t Common_month_day[MONTH_PER_YEAR] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; // 平年

static uint8_t ComputingWeek(uint16_t Year, uint8_t Month, uint8_t Day)
{
  uint8_t CenturyCode = 0, YearCode = 0, MonthCode = 0, DayCode = 0, Week = 0;

  if ((Month == 1) || (Month == 2))
  {
    CenturyCode = (Year - 1) / 100;
    YearCode = (Year - 1) % 100;
    MonthCode = Month + 12;
    DayCode = Day;
  }
  else
  {
    CenturyCode = Year / 100;
    YearCode = Year % 100;
    MonthCode = Month;
    DayCode = Day;
  }

  Week = YearCode + YearCode / 4 + CenturyCode / 4 - 2 * CenturyCode + 26 * (MonthCode + 1) / 10 + DayCode - 1;
  Week = Week > 0 ? (Week % 7) : ((Week % 7) + 7);

  return Week;
}

static bool DetermineWhetherIsLeapYear(uint16_t Year)
{
  if ((Year % 400) == 0)
    return true;
  else if ((Year % 100) == 0)
    return false;
  else if ((Year % 4) == 0)
    return true;

  return false;
}

static uint8_t ObtainTheTotalNumberOfDaysInTheMonth(uint8_t Month, uint16_t Year)
{
  if ((Month == 0) || (Month > MONTH_PER_YEAR))
    return g_day_per_mon[1] + DetermineWhetherIsLeapYear(Year);

  if (Month != 2)
    return g_day_per_mon[Month - 1];

  return g_day_per_mon[1] + DetermineWhetherIsLeapYear(Year);
}

static uint32_t ObtainTheTotalNumberOfDaysInTheYear(uint16_t Year)
{
  return ((((Year % 4 == 0) && (Year % 100 != 0)) || (Year % 400 == 0)) ? 366 : 365);
}

uint32_t MakeTimestamp(TimeFormat_t *pTransitionTimeIn)
{
  uint32_t UnixTime = 0, Temp = 0;

  if (pTransitionTimeIn != NULL)
  {
    for (Temp = UTC_BASE_YEAR; Temp < pTransitionTimeIn->Year; Temp++)
    {
      UnixTime += ObtainTheTotalNumberOfDaysInTheYear(Temp) * SEC_PER_DAY;
    }

    for (Temp = 1; Temp < pTransitionTimeIn->Month; Temp++)
    {
      UnixTime += SEC_PER_DAY * ((ObtainTheTotalNumberOfDaysInTheYear(pTransitionTimeIn->Year) == 366) ? (Leap_month_day[Temp - 1]) : (Common_month_day[Temp - 1]));
    }

    UnixTime += (pTransitionTimeIn->Date - 1) * SEC_PER_DAY;
    UnixTime += pTransitionTimeIn->Hour * SEC_PER_HOUR;
    UnixTime += pTransitionTimeIn->Minute * 60;
    UnixTime += pTransitionTimeIn->Second;
  }

  return UnixTime;
}

void TimestampToNormalTime(uint32_t UtcSecond, bool DaylightSaving, TimeFormat_t *pTransitionTimeOut)
{
  if (pTransitionTimeOut != NULL)
  {
    if ((UtcSecond > 0) && (((UINT32_MAX - UtcSecond) - SEC_PER_HOUR) > 0))
    {
      uint8_t Month = 0;
      uint16_t Year = 0, Day = 0;
      uint32_t Second = 0, Date = 0;

      if (DaylightSaving)
        UtcSecond += SEC_PER_HOUR;

      Second = UtcSecond % SEC_PER_DAY;
      pTransitionTimeOut->Hour = Second / SEC_PER_HOUR;

      Second %= SEC_PER_HOUR;
      pTransitionTimeOut->Minute = Second / SEC_PER_MIN;

      pTransitionTimeOut->Second = Second % SEC_PER_MIN;

      Date = UtcSecond / SEC_PER_DAY;
      for (Year = UTC_BASE_YEAR; Date > 0; Year++)
      {
        Day = (DAY_PER_YEAR + DetermineWhetherIsLeapYear(Year));
        if (Date >= Day)
        {
          Date -= Day;
        }
        else
          break;
      }

      pTransitionTimeOut->Year = Year;

      for (Month = 1; Month < MONTH_PER_YEAR; Month++)
      {
        Day = ObtainTheTotalNumberOfDaysInTheMonth(Month, Year);
        if (Date >= Day)
        {
          Date -= Day;
        }
        else
          break;
      }

      pTransitionTimeOut->Month = Month;
      pTransitionTimeOut->Date = (uint8_t)(Date + 1);
      pTransitionTimeOut->Week = ComputingWeek(pTransitionTimeOut->Year, pTransitionTimeOut->Month, pTransitionTimeOut->Date);
    }
  }
}
